﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using seiacpscs_a2.datastructures;
using System.Diagnostics;
using seiacpscs_a2.utilities;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace seiacpscs_a2
{
    class Program
    {
        const String EOL = "\r\n";
        static String dataStructureName;
        static String inputFile;
        static String outputFile;
        static String strategy;
        static int[] items = null;
        static DataStructure dataStructure = null;
        static long fileParsingExecutionTime;
        static long dataStructureCreationTime;
        static long memoryUsed = 0;
        static Action<String> println = Console.WriteLine;
        

        static void Main(string[] args)
        {
            Contract.Requires(args != null && args.Length >= 3, "Usage: <data_structure> <input_file> <output_file> [<hashing_strategy>]");
            dataStructureName = args[0];
            inputFile = args[1];
            outputFile = args[2];
            strategy = (args.Length == 4) ? args[3] : null;
            var tupleAvgHitMiss = new Tuple<List<double>, List<double>>(new List<double>(), new List<double>());
            StringBuilder output = new StringBuilder();

            try
            {
                var parser = new FileParser(inputFile);              
                items = GetExecutionTime<int[]>( parser.GetNumbers, out fileParsingExecutionTime);
                long memoryUsedBefore = MeasureMemoryUsed();
                dataStructure = GetExecutionTime<DataStructure>( () => DataStructureFactory.create(dataStructureName,strategy,items),out dataStructureCreationTime);
                long memoryUsedAfter = MeasureMemoryUsed();
                memoryUsed = memoryUsedAfter - memoryUsedBefore;
            }
            catch (ArgumentException e)
            {
                println(e.Message);
            }
            displayHelpWelcomeHeader();

            while (true)
            {
                string input = Console.ReadLine();
                if (shouldStop(input)) break;

                try
                {
                    int value = Convert.ToInt32(input);
                    Boolean found = dataStructure.contains(value);
                    String result = String.Format("Value {0} was {1} with a number of access of {2}", value, formatBooleanToString(found), dataStructure.NumberOfAccesses);
                    println(result);
                    addNbrOfAccesses(found, dataStructure.NumberOfAccesses, tupleAvgHitMiss);
                    displayHitMissAverages(tupleAvgHitMiss);
                    //setHitMissCount(found);
                    output.Append(result + EOL);
                }
                catch (FormatException e)
                {
                    println("please enter a valid integer!");
                }
            }
            output.Append(generateOutputStatistics(tupleAvgHitMiss));
            writeToFile(output.ToString(),outputFile,false);
            println("finished reading results, the ouput file " + outputFile + " was generated...");

        }

        /*
        private static void setHitMissCount(Boolean found)
        {
            if (found)
                nbrHits++;
            else
                nbrMisses++;
        }
         */

        private static String generateOutputStatistics(Tuple<List<double>, List<double>> hitMissAverage)
        {
            StringBuilder builder = new StringBuilder();

            builder.Append("==================================================================================" + EOL);
            builder.Append("File Parsing Time (Nano Seconds) : " + fileParsingExecutionTime + EOL);
            builder.Append("DataStructure Name : " + dataStructure.DataStructureName + EOL);
            builder.Append("DataStructure Creation Time (Nano Seconds) : " + dataStructureCreationTime + EOL);
            builder.Append("Memory used (Bytes) : " + memoryUsed + EOL);
            builder.Append(String.Format("Number of missed searches is {0} " + EOL, hitMissAverage.Item2.Count));
            builder.Append(String.Format("Number of found searches is {0} " + EOL, hitMissAverage.Item1.Count));
            builder.Append(String.Format("Average Number of accesses for missed searches is {0} " + EOL, hitMissAverage.Item2.Average()));
            builder.Append(String.Format("Average Number of accesses for searches found is {0} " + EOL, hitMissAverage.Item1.Average()));

            return builder.ToString();
        }


        private static T GetExecutionTime<T>(Func<T> funcCall, out long executionTime) 
        {
            Stopwatch stopWatch = new Stopwatch(); // Statistics Code
            executionTime = 0;
            T result = default(T);
            stopWatch.Start(); // Statistics Code
            result = funcCall();
            stopWatch.Stop(); // Statistics Code
            executionTime = (stopWatch.ElapsedTicks * 1000L * 1000L * 1000L);
            return result;   
        }

        private static void addNbrOfAccesses(Boolean found, int nbrOfAccesses, Tuple<List<double>, List<double>> hitMissAverages)
        {
            if (found) 
            {
                hitMissAverages.Item1.Add(nbrOfAccesses);
            }
            else
            {
                hitMissAverages.Item2.Add(nbrOfAccesses);
            }
        }

        private static bool shouldStop(String input)
        {
            return "exit".Equals(input) || "end".Equals(input) || "Ctr+Z".Equals(input);
        }

        private static void displayHitMissAverages(Tuple<List<double>, List<double>> hitMissAverages)
        {
            println(string.Format("The average Number of Accesses for found items is {0} ", getAverageOrElse(hitMissAverages.Item1,0)));
            println(string.Format("The average Number of Accesses for items not found is {0} ", getAverageOrElse(hitMissAverages.Item2, 0)));
        }

        private static double getAverageOrElse(List<double> items,double defaultValue)
        {
            return items.Count > 0 ? items.Average() : defaultValue;
        }

        private static void displayHelpWelcomeHeader() 
        {
		    println("COM411 Assignment 2 Project");
		    println("	-To search for an item enter the value and press enter");
		    println("	-To exit type 'exit', 'end'");
	    }

        private static String formatBooleanToString(bool found) 
        {
		    return (found)? "found" : "not found";
	    }

        private static long GetObjectSize(object obj)
        {
            var bf = new BinaryFormatter();
            var ms = new MemoryStream();
            bf.Serialize(ms, obj);
            var size = ms.Length;
            ms.Dispose();
            return size;
        }


        private static long MeasureMemoryUsed()
        {
            long memoryUsed = 0;
            Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            memoryUsed = currentProcess.WorkingSet64;
            return memoryUsed;
        }

        private static void writeToFile(String output, String filePath, bool isAppendModeOn)
        {
            StreamWriter writer = new StreamWriter(filePath, isAppendModeOn);
            writer.Write(output);
            writer.Close();
        }

    }
}
